// Example 2-1. A simple OpenCV program that loads an image from disk and displays it
// on the screen
#include "ctime"
#include <algorithm>
#include <bits/types/clock_t.h>
#include <cstdint>
#include <ctime>
#include <opencv2/core/base.hpp>
#include <opencv2/core/cvstd.hpp>
#include <opencv2/core/hal/interface.h>
#include <opencv2/core/mat.hpp>
#include <opencv2/core/matx.hpp>
#include <opencv2/core/types.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/opencv.hpp>
#include <vector>
clock_t start, end;

void help(char** argv)
{
    std::cout << "\n"
              << "A simple OpenCV program that loads and displays an image from disk\n"
              << argv[0] << " <path/filename>\n"
              << "For example:\n"
              << argv[0] << " ../fruits.jpg\n"
              << std::endl;
}

using namespace std;
using namespace cv;
int show_img(cv::Mat img, cv::String img_name)
{
    cv::namedWindow(img_name, cv::WINDOW_AUTOSIZE);
    cv::imshow(img_name, img);
    cv::waitKey(0);
    cv::destroyWindow(img_name);
    return 0;
}

cv::Mat adaptiveThresholdAndErode(cv::Mat src, cv::AdaptiveThresholdTypes adaptiveMethod = cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::ThresholdTypes thresholdType = cv::THRESH_BINARY_INV, int blockSize = 111, int C = 20, int erode_iterations = 2, int dilate_iterations = 2)
{
    cv::Mat dst;
    // 自适应阈值分割
    cv::adaptiveThreshold(src, dst, 255,
        adaptiveMethod, thresholdType,
        blockSize, C);
    // show_img(dst, "自适应阈值分割后");
    cv::erode(dst, dst, cv::Mat(), Point(-1, -1), erode_iterations);
    // show_img(dst, "腐蚀后");
    auto kernel = cv::Mat::ones(5, 5, CV_8UC1);
    // cout << kernel << endl;
    // cout << "kernel.size: " << kernel.size() << endl;
    cv::dilate(dst, dst, kernel, Point(-1, -1), dilate_iterations);
    // show_img(dst, "膨胀后");

    return dst;
}
// Mat get_contour_img(Mat img_left_u8_c1, vector<vector<Point>> contours, int contour_index)
// {
//     auto img_left_u8_black_background = Mat::zeros(img_left_u8_c1.size, CV_8UC1);
// }

double get_distance_from_two_point(Point current_point, Point next_point)
{
    double distance;
    distance = sqrt(pow(current_point.x - next_point.x, 2) + pow(current_point.y - next_point.y, 2));
    return distance;
}

int get_next_point_satisfing_min_distance(vector<Point> contour, int current_point_index, int min_adjacent_distance = 3)
{
    auto current_point = contour[current_point_index];
    auto offset = 1;
    auto contour_len = contour.size();
    auto next_point_index = current_point_index + offset;

    if (next_point_index >= contour_len) {
        next_point_index = next_point_index - contour_len;
    }

    auto next_point = contour[next_point_index];
    auto distance = get_distance_from_two_point(current_point, next_point);

    while (next_point_index < contour_len && distance < min_adjacent_distance) {
        offset += 1;
        next_point_index = current_point_index + offset;
        if (next_point_index >= contour_len)
            break;
        next_point = contour[next_point_index];
        distance = get_distance_from_two_point(current_point, next_point);
        // cout << "distance: " << distance <<  endl;
    }

    if (next_point_index < contour_len) {
        return next_point_index;
    } else {
        return -1;
    }
}

int get_previous_point_satisfing_min_distance(vector<Point> contour,
    int current_point_index, int min_adjacent_distance = 3)
{
    auto current_point = contour[current_point_index];
    auto offset = 1;
    auto contour_len = contour.size();
    auto pre_point_index = current_point_index - offset;
    // cout << "test1" << endl;

    if (pre_point_index < 0) {
        pre_point_index = pre_point_index + contour_len;
    }
    // cout << "test2" << endl;
    auto pre_point = contour[pre_point_index];
    auto distance = get_distance_from_two_point(pre_point, current_point);

    // cout << "test3" << endl;
    while (pre_point_index >= 0 && distance < min_adjacent_distance) {
        offset += 1;
        pre_point_index = current_point_index - offset;
        if (pre_point_index < 0)
            break;
        // cout << "月结地址: "  << pre_point_index << endl;
        pre_point = contour[pre_point_index];
        distance = get_distance_from_two_point(current_point, pre_point);
    }

    if (pre_point_index >= 0) {
        return pre_point_index;
    } else {
        return -1;
    }
}

auto get_angle_from_constant_point(Point p0, Point p1, Point p2)
{
    auto v1 = p0 - p1;
    auto v2 = p2 - p1;
    auto first_angle = fastAtan2(v1.y, v1.x);
    auto second_angle = fastAtan2(v2.y, v2.x);
    auto result = second_angle - first_angle;
    if (result < -180) {
        result = result + 360;
    } else if (result > 180) {
        result = result - 2 * 180;
    }
    return result;
}

auto get_concave_point_from_contour(vector<Point> contour)
{
    auto contour_len = contour.size();
    // cout << "contour.size(): "  << contour_len << endl;
    auto pre_point_index = 0;
    auto next_point_index = get_next_point_satisfing_min_distance(contour, pre_point_index);
    // cout << "next_point_index: " << next_point_index << endl;
    vector<Point> candidate_point_list;

    for (auto current_point_index = next_point_index; current_point_index < contour_len; current_point_index++) {
        auto current_point = contour[current_point_index];
        pre_point_index = get_previous_point_satisfing_min_distance(contour, current_point_index);
        // cout << "pre_point_index: " << pre_point_index << endl;
        next_point_index = get_next_point_satisfing_min_distance(contour, next_point_index);

        // cout << "next_point_index: " << next_point_index << endl;
        if (pre_point_index == -1
            || next_point_index == -1)
            break;
        auto pre_point = contour[pre_point_index];
        auto next_point = contour[next_point_index];
        auto angle = get_angle_from_constant_point(pre_point, current_point, next_point);
        // cout << "angle : " << angle << endl;
        if (angle < 0 && abs(angle) < 130) {
            candidate_point_list.push_back(current_point);
        }
    }
    return candidate_point_list;
}

int main(int argc, char** argv)
{

    // if (argc != 2) {
    // 	help(argv);
    // 	return 0;
    // }

    cv::Mat img_left_u8_c1 = cv::imread("../img_left_u8.png", -1);
    auto start = clock();

    auto test_time = 1;

    for (auto i = 0; i < test_time; i++) {

        if (img_left_u8_c1.empty())
            return -1;

        Mat dst;
        cv::threshold(img_left_u8_c1, dst, 0, 255, THRESH_OTSU | THRESH_BINARY_INV);
        auto kernel = Mat::ones(3, 3, CV_8UC1);
        Mat opening;
        morphologyEx(dst, opening, MORPH_OPEN, kernel, Point(-1, -1), 2);
        Mat sure_bg;
        dilate(opening, sure_bg, kernel, Point(-1, -1), 3);
        Mat dist_transform;

        // show_img(opening, "opening");
        distanceTransform(opening, dist_transform, DIST_L2, 5,CV_8U);
        float max_value = *max_element(dist_transform.begin<float>(),dist_transform.end<float>());
        Mat sure_fg;
        threshold(dist_transform, sure_fg, max_value * 0.3, 255,THRESH_BINARY);
        // cout << dist_transform << endl;
        show_img(dist_transform, "dist_transform");
    }

    auto end = clock();
    auto total_time = (double)(end - start) / CLOCKS_PER_SEC;
    cout << "total time: " << total_time * 1000 << "ms" << endl;
    cout << "average time: " << total_time * 1000 / test_time << "ms" << endl;

    // cout << get_distance_from_two_point(Point(1,2), Point(2,3)) << endl;;
    return 0;
}
